package cn.chinaunicom.mini.program.dingtalk.openapi.service;

import java.util.concurrent.TimeUnit;

import cn.chinaunicom.mini.program.dingtalk.openapi.client.IDingTalkClient;
import com.alibaba.fastjson2.JSON;
import com.aliyun.dingtalkoauth2_1_0.models.*;
import com.aliyun.tea.TeaException;
import com.taobao.api.ApiException;
import org.springframework.stereotype.Component;

import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.anno.Cached;
import com.aliyun.teautil.models.RuntimeOptions;

import cn.chinaunicom.mini.program.dingtalk.constants.DingTalkRedisKeys;
import lombok.extern.slf4j.Slf4j;

/**
 * @ProjectName: dingtalk-property
 * @Package: cn.chinaunicom.mini.program.dingtalk.openapi.service
 * @ClassName: AuthService
 * @Description: 钉钉开放平台各种认证接口
 * @Author: wangjc
 * @CreateDate: 2024/6/6
 * @UpdateUser: wangjc
 * @UpdateDate: 2024/6/6
 * @UpdateRemark: 更新内容
 * @Version: 1.0
 */
@Slf4j
@Component
public class DingTalkAuthAPI {

	/**
	 * 获取企业access_token 缓存有效时间2小时
	 * 
	 * @return
	 */
	@Cached(name = DingTalkRedisKeys.CORP_ACCESS_TOKEN, key = "#req.authCorpId", cacheType = CacheType.REMOTE, timeUnit = TimeUnit.MINUTES, expire = 120)
	public String getCorpAccessToken(com.aliyun.dingtalkoauth2_1_0.models.GetCorpAccessTokenRequest req) throws ApiException {
		try {
			com.aliyun.dingtalkoauth2_1_0.Client client = createClient();
			GetCorpAccessTokenResponse response = client.getCorpAccessToken(req);
			log.debug("钉钉开放平台接口响应体：{}", JSON.toJSONString(response.getBody()));
			return response.getBody().getAccessToken();
		} catch (Exception e) {
			log.error("dingtalk open api error: {}", e);
			throw new ApiException(e.getCause());
		}
	}

	/**
	 * 获取套件访问令牌。 该方法用于通过钉钉开放平台的套件密钥、密钥和套件票据来获取套件访问令牌。
	 * 套件访问令牌是访问钉钉开放平台API的身份凭证，用于第三方应用与钉钉进行通信。
	 *
	 * @param req suiteKey 套件的密钥，用于标识套件的唯一性。 suiteSecret 套件的密钥，用于验证套件的身份。
	 *            suiteTicket 套件票据，是钉钉颁发的用于获取套件访问令牌的凭证。
	 * @return 返回套件访问令牌。如果获取失败或出现异常，则返回null。
	 */
	public String getSuiteAccessToken(com.aliyun.dingtalkoauth2_1_0.models.GetSuiteAccessTokenRequest req) throws ApiException {
		try {
			// 创建钉钉开放平台的客户端对象，用于发送请求。
			com.aliyun.dingtalkoauth2_1_0.Client client = createClient();
			// 发送请求，获取套件访问令牌的响应。
			GetSuiteAccessTokenResponse response = client.getSuiteAccessToken(req);
			log.debug("钉钉开放平台接口响应体：{}", JSON.toJSONString(response.getBody()));
			// 从响应中提取套件访问令牌，并返回。
			return response.getBody().getAccessToken();
		} catch (Exception e) {
			// 记录获取套件访问令牌过程中出现的异常。
			log.error("dingtalk open api error: {}", e);
			throw new ApiException(e.getCause());
		}
	}

	/**
	 * 获取企业内部应用的accessToken
	 *
	 * @param clientId
	 * @param clientSecret
	 * @return
	 */
	@Cached(name = DingTalkRedisKeys.ACCESS_TOKEN, key = "#clientId", cacheType = CacheType.REMOTE, timeUnit = TimeUnit.MINUTES, expire = 120)
	public String getAccessToken(String clientId, String clientSecret) throws ApiException {
		try {
			com.aliyun.dingtalkoauth2_1_0.Client client = createClient();
			com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenRequest getAccessTokenRequest = new com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenRequest()
					.setAppKey(clientId).setAppSecret(clientSecret);
			GetAccessTokenResponse response = client.getAccessToken(getAccessTokenRequest);
			log.debug("钉钉开放平台接口响应体：{}", JSON.toJSONString(response.getBody()));
			return response.getBody().getAccessToken();
		} catch (Exception e) {
			log.error("dingtalk open api error: {}", e);
			throw new ApiException(e.getCause());
		}
	}

	/**
	 * 获取SSO访问令牌的方法。
	 *
	 * 通过调用钉钉开放平台的接口，传入请求参数来获取SSO访问令牌。
	 * 此方法封装了与钉钉开放平台的交互过程，对外提供了一个简洁的接口。
	 *
	 * @param req 获取SSO访问令牌的请求参数，包含必要的认证信息。
	 * @return 返回从钉钉开放平台获取的SSO访问令牌。
	 * @throws ApiException 如果调用钉钉开放平台接口失败，将抛出此异常。
	 */
	public String getSsoAccessToken(GetSsoAccessTokenRequest req) throws ApiException {
	    try {
	        // 创建钉钉开放平台的客户端对象，用于发送请求。
	        com.aliyun.dingtalkoauth2_1_0.Client client = createClient();

	        // 调用钉钉开放平台的接口，获取SSO访问令牌。
	        GetSsoAccessTokenResponse response = client.getSsoAccessToken(req);

	        // 记录接口响应体的日志，方便调试和排查问题。
	        log.debug("钉钉开放平台接口响应体：{}", JSON.toJSONString(response.getBody()));

	        // 返回获取到的SSO访问令牌。
	        return response.getBody().getAccessToken();
	    } catch (Exception e) {
	        // 记录调用钉钉开放平台接口失败的日志，包括错误详情。
	        log.error("dingtalk open api error: {}", e);

	        // 将调用钉钉开放平台接口失败的异常包装成ApiException，并抛出。
	        // 这样上层调用者可以根据ApiException来处理错误情况。
	        throw new ApiException(e.getCause());
	    }
	}

	/**
	 * 通过SSO授权码获取用户信息。
	 * 使用SSO（Single Sign-On）授权码和访问令牌，调用钉钉开放平台接口获取用户详细信息。
	 * 此方法封装了对钉钉开放平台API的调用，处理了异常，并返回用户信息的响应体。
	 *
	 * @param authCode SSO授权码，通过钉钉SSO登录流程获取。
	 * @param accessToken 访问令牌，用于授权访问钉钉开放平台资源。
	 * @return GetSsoUserInfoResponseBody 用户信息的响应体，包含用户详细信息。
	 * @throws ApiException 如果调用钉钉开放平台接口失败，抛出此异常。
	 */
	public GetSsoUserInfoResponseBody getSsoUserInfo(String authCode, String accessToken) throws ApiException {
	    try {
	        // 创建钉钉开放平台的客户端实例
	        com.aliyun.dingtalkoauth2_1_0.Client client = createClient();

	        // 设置请求头，包含访问令牌
	        // 设置header token
	        GetSsoUserInfoHeaders header = new GetSsoUserInfoHeaders();
	        header.xAcsDingtalkAccessToken = accessToken;

	        // 创建请求对象，设置授权码
	        GetSsoUserInfoRequest req = new GetSsoUserInfoRequest().setCode(authCode);

	        // 调用钉钉开放平台接口，获取用户信息
	        GetSsoUserInfoResponse response = client.getSsoUserInfoWithOptions(req, header, new RuntimeOptions());

	        // 记录日志，方便问题排查
	        log.info("dingtalk open api response: {}", response.getBody());

	        // 返回用户信息响应体
	        return response.getBody();
	    } catch (Exception e) {
	        // 记录异常信息，方便问题排查
	        log.error("dingtalk open api error: {}", e);
	        // 将内部异常封装成ApiException，再次抛出
	        throw new ApiException(e.getCause());
	    }
	}

	/**
	 * 获取用户令牌。
	 *
	 * 该方法用于通过钉钉开放平台的API获取用户令牌。用户令牌是访问钉钉接口的身份凭证， 通过此方法可以为应用程序获取必要的访问权限。
	 *
	 * @param req 获取用户令牌的请求对象，包含必要的请求参数。
	 * @return 用户令牌的访问令牌部分。如果获取失败或出现异常，返回null。
	 */
	public String getUserToken(GetUserTokenRequest req) throws ApiException {
		try {
			// 创建钉钉API客户端
			com.aliyun.dingtalkoauth2_1_0.Client client = createClient();
			// 发起获取用户令牌的请求，并获取响应
			GetUserTokenResponse response = client.getUserToken(req);
			// 从响应中提取并返回访问令牌
			return response.getBody().getAccessToken();
		} catch (Exception e) {
			// 记录获取用户令牌过程中出现的异常
			log.error("dingtalk open api error: {}", e);
			throw new ApiException(e.getCause());
		}
	}

	/**
	 * 根据企业授权ID和访问令牌获取企业授权信息。
	 * 第三方应用可以通过该方法获取授权企业的相关信息，包括企业ID、企业名称、企业logo等。
	 * @param authCorpId 企业授权ID，用于标识授权的企业。
	 * @param accessToken 访问令牌，用于验证请求的合法性。
	 * @return 返回企业授权信息的响应体，如果请求失败则返回null。
	 * @throws TeaException 如果请求过程中出现错误，则抛出TeaException。
	 */
	public GetAuthInfoResponseBody getAppAuthInfoByCorpId(String authCorpId, String accessToken) {
	    // 创建请求头，设置访问令牌。
	    GetAuthInfoHeaders getAuthInfoHeaders = new GetAuthInfoHeaders();
	    getAuthInfoHeaders.xAcsDingtalkAccessToken = accessToken;

	    // 创建请求体，设置授权企业ID。
	    GetAuthInfoRequest getAuthInfoRequest = new GetAuthInfoRequest().setAuthCorpId(authCorpId);

	    try {
	        // 发起请求，获取企业授权信息。
	        GetAuthInfoResponse response = this.createClient().getAuthInfoWithOptions(getAuthInfoRequest, getAuthInfoHeaders, new RuntimeOptions());
	        return response.getBody();
	    } catch (TeaException err) {
	        // 如果请求抛出TeaException，记录错误信息。
	        if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
	            log.error("dingtalk open api error {}", err);
	        }
	    } catch (Exception _err) {
	        // 如果请求抛出其他异常，转换为TeaException并记录错误信息。
	        TeaException err = new TeaException(_err.getMessage(), _err);
	        if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
	            log.error("dingtalk open api Exception  {}", err);
	        }
	    }
	    // 如果请求失败，返回null。
	    return null;
	}

	/**
	 * 使用 Token 初始化账号Client
	 * 
	 * @return Client
	 * @throws Exception
	 */
	public com.aliyun.dingtalkoauth2_1_0.Client createClient() throws Exception {
		return new com.aliyun.dingtalkoauth2_1_0.Client(IDingTalkClient.genDingTalkClientConfig());
	}

}
